package com.mindata.blockchain.socket.DGL.queue;

import cn.hutool.core.bean.BeanUtil;
import com.mindata.blockchain.block.block.Block;
import com.mindata.blockchain.block.block.DGLBlock;
import com.mindata.blockchain.common.AppId;
import com.mindata.blockchain.common.Commonvalue;
import com.mindata.blockchain.core.event.DGLAddBlocksEvent;
import com.mindata.blockchain.socket.DGL.event.DGLMsgCommitEvent;
import com.mindata.blockchain.socket.DGL.msg.DGLVoteMsg;
import com.mindata.blockchain.socket.common.msg.MsgType;
import com.mindata.blockchain.socket.common.queue.base.AbstractVoteMsgQueue;
import com.mindata.blockchain.socket.pbft.msg.VoteMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * Prepare阶段的消息队列
 *
 * @author wuweifeng wrote on 2018/4/25.
 */
@Component
public class DGLPrepareMsgQueue extends AbstractVoteMsgQueue {
    @Resource
    private DGLCommitMsgQueue dglCommitMsgQueue;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Resource
    private Commonvalue commonvalue;

    /**
     * 收到节点（包括自己）针对某Block的Prepare消息
     *
     * @param voteMsg  voteMsg
     * @param voteMsgs
     */
    @Override
    protected void deal(VoteMsg voteMsg, List<VoteMsg> voteMsgs) {
//        logger.error("deal DGLPrepareMsg");
        String hash = voteMsg.getHash();
        DGLVoteMsg dglCommitMsg = new DGLVoteMsg();
        BeanUtil.copyProperties(voteMsg, dglCommitMsg);
//        logger.error("voteMsg 转换成 DGLVoteMsg ：{}", dglCommitMsg);
        dglCommitMsg.setVoteType(MsgType.DLGCOMMIT);
        dglCommitMsg.setAppId(AppId.getValue());
//        logger.error("voteMsg 转换成 DGLVoteMsg ：{}", dglCommitMsg);
        //开始校验并决定是否进入commit阶段
        //校验该vote是否合法
        if (dglCommitMsgQueue.hasOtherConfirm(hash, voteMsg.getNumber())) {
//            logger.error("DGLVoteMsg  hasOtherConfirm  yes");
            agree(dglCommitMsg, false);
        } else {
            //开始校验拜占庭数量，如果agree的超过2f + 1，就commit
            long agreeCount = voteMsgs.stream().filter(VoteMsg::isAgree).count();
//            logger.warn("区块{}的 prepareMsg Agree 的消息数量: {}", voteMsg.getHash(), agreeCount);
            long unAgreeCount = voteMsgs.size() - agreeCount;

            //开始发出commit的同意or拒绝的消息
//            logger.error("DGLVoteMsg  hasOtherConfirm  none");
//            logger.error("agreeCount：{},GlobalAgreeSize：{}", agreeCount, commonvalue.getGlobalAgreeSize());
            if (agreeCount >= commonvalue.getGlobalAgreeSize()) {
//                logger.error("达到阈值 提交到CommitQueue");
                agree(dglCommitMsg, true);
                // 参考unAgreeCount >= pbftSize() + 1
            } else if (commonvalue.getShardnum()>=4&&unAgreeCount >= commonvalue.getShardnum() - commonvalue.getGlobalAgreeSize()) {
//                logger.error("prepare 未达到阈值");
                agree(dglCommitMsg, false);
            }
        }

    }


    private void agree(DGLVoteMsg dglCommitMsg, boolean flag) {
        logger.error("DGLPrepare阶段完毕，是否进入DGLcommit的标志是：{}", flag);
        //发出拒绝commit的消息
        dglCommitMsg.setAgree(flag);
        voteStateConcurrentHashMap.put(dglCommitMsg.getHash(), flag);
        eventPublisher.publishEvent(new DGLMsgCommitEvent(dglCommitMsg));
    }

    /**
     * 判断大家是否已对其他的Block达成共识，如果true，则拒绝即将进入队列的Block
     *
     * @param hash hash
     * @return 是否存在
     */
    public boolean otherConfirm(String hash, int number) {
        if (dglCommitMsgQueue.hasOtherConfirm(hash, number)) {
            return true;
        }
        return hasOtherConfirm(hash, number);
    }

    /**
     * 新区块生成后，clear掉map中number比区块小的所有数据
     *
     * @param dglAddBlocksEvent addBlockEvent
     */
    @Order(3)
    @EventListener(DGLAddBlocksEvent.class)
    public void blockGenerated(DGLAddBlocksEvent dglAddBlocksEvent) {
        DGLBlock dglBlock = (DGLBlock) dglAddBlocksEvent.getSource();
        clearOldBlockHash(dglBlock.getDglBlockHeader().getNumber());
    }
}
